package squarePackage;



import java.util.List;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;


import Enumerations.Slippery;
import Exceptions.IllegalHumidityException;
import Exceptions.IllegalMinMaxTemperatureException;
import Exceptions.IllegalTemperatureException;
import be.kuleuven.cs.som.annotate.*;


/**
 * A class of squares in a game, being the smallest unit of location, with own
 * temperature, humidity, slipperiness, inhabitability & borders.
 * 
 * @invar       the temperature has to be a valid temperature
 *                      |isValidTemperature(this.getTemperature())
 * @invar       the temperature limits have to be valid
 *                      |isValidMinMaxTemperature(this.getMIN_TEMPERATURE,this.getMAX_TEMPERATURE)
 * @invar       the humidity of the square has to be a valid percentage
 *                      |isValidHumidity(this.getHumidity())
 * 
 * @version 24/3/2011 20.42
 * @author Pieter & An
 * 
 */
public class Square {

        /**
         * A constructor to initialize a square with given temperature and humidity
         * 
         * @param       temperature
         *                      the temperature you wish to initialize the square with
         * @param       humidity
         *                      the humidity you wish to initialize the square with
         * @post        the temperature will be set to the desired value
         *                      |new.getTemperature() == temperature
         * @post        the humidity will be set to the desired value
         *                      |new.getHumidity() == humidity
         * @post        the square will not have any borders
         *                      |new.hasNumberBorders() == 0
         * @throws      IllegalTemperatureException
         *                      If the temperature is not valid
         *                      |!(isValidTemperature)
         *              IllegalHumidityException
         *              		If the humidity is not valid
         *              		|!(isValidHumidity)
         */
        public Square(double temperature, BigDecimal humidity) throws IllegalTemperatureException{
          
        	if (!isValidHumidity(humidity)){
        		throw new IllegalHumidityException(humidity, this);
        	}
                                        
                                                this.setTemperature(temperature);
                                                this.setHumidity(humidity);
                                                this.setSlippery();

                
        }
        /**
         * A constructor to create a square without any arguments
         *
         * @post        the temperature will be set to 0°C
         *                      |new.getTemperature() = 0
         * @post        the humidity will be set to 50%
         *                      |new.getHumidity() = 50
         * @post        the square will not have any borders
         *                      |new.hasNumberBorders() == 0 
         */
        public Square(){
                this(0,new BigDecimal("0.50"));
        }


        /**
         * A variable registering the square temperature in °C
         */
        private double temperature;

        /**
         * A method returning the current temperature of the square
         */
        @Basic 
        public double getTemperature() {
                return this.temperature;
        }

        /**
         * A method to check whether the temperature is valid
         * 
         * @param       temperature
         *                      The temperature you wish to check
         * @return      True if the temperature is between the outer temperature limits
         *                      |result == (this.getMIN_TEMPERATURE() <= temperature) 
         *                      |       &&(temperature <= this.getMAX_TEMPERATURE()) 
         *                      |               && (temperature !=null))
         */
        public boolean isValidTemperature(double temperature) {
                return (this.getMIN_TEMPERATURE() <= temperature) 
                                && (temperature <= this.getMAX_TEMPERATURE());
                        
                
                
        }

        /**
         * A method to check whether the minimum and maximum temperatures are valid for the square
         * 
         * @param       minT
         *                      the desired minimum temperature you want to check
         * @param       maxT
         *                      the desired maximum temperature you want to check
         * @return      true if minT <= maxT 
         *                      |if(minT <= maxT)
         *                      |       result == true  
         */
        public boolean isValidMinMaxTemperature(double minT, double maxT){
                int i = Double.compare(minT,maxT);
                if(i<=0)
                        return true;
                return false;
                

        }
        
        /**
         * A method to set the boundary minimum and maximum temperature
         *
         * @param       minT
         *                      the minimum square temperature you want to set
         * @param       maxT
         *                      the maximum square temperature you want to set
         * @post        the square's minimum and maximum temperature will be set to the desired values
         *                      |new.getMIN_TEMPERATURE() = minT && new.getMAX_TEMPERATURE() = maxT
         * @throws  IllegalMinMaxTemperatureException
         *                      when the boundary temperatures are not valid
         *                      |!isValidMinMaxTemperature(minT, maxT)
         */
        public void setMinMaxTemperature(double minT, double maxT) throws IllegalMinMaxTemperatureException{
                if(!isValidMinMaxTemperature(minT, maxT))
                        throw new IllegalMinMaxTemperatureException(minT,maxT,this);
                this.MIN_TEMPERATURE = minT;
                this.MAX_TEMPERATURE = maxT;
        }
        
        
        /**
         * A method to set the temperature at the desired temperature
         *
         * @param       temperature
         *          The temperature we want the square to be set up with
         * @post        The square temperature will be set to the desired temperature 
         *              |new.getTemperature() = temperature 
         * @throws      IllegalTemperatureException
         *          If the temperature is not valid
         *          |!isValidTemperature(temperature)
         */
        public void setTemperature(double temperature)
                throws IllegalTemperatureException {
                if(!isValidTemperature(temperature))
                        throw new IllegalTemperatureException(temperature, this);
                this.temperature=temperature;
                
        
                }
                        
        /**
         * Constant reflecting the lowest possible temperature for a square
         */
        private double MIN_TEMPERATURE = -200;

        /**
         * Constant reflecting the highest possible temperature for a square
         */
        private  double MAX_TEMPERATURE = 5000;
        
        /**
         * A method to inspect the lowest possible square temperature
         *
         * @return      The lowest possible square temperature
         *                      |result == this.MIN_TEMPERATURE
         */
        @Basic
        public double getMIN_TEMPERATURE(){
                return MIN_TEMPERATURE;
        }
        
        /**
         * A method to inspect the highest possible square temperature
         * 
         * @return      The highest possible square temperature
         *                      |result == this.MAX_TEMPERATURE
         */
        @Basic
        public double getMAX_TEMPERATURE(){
                return MAX_TEMPERATURE;
        }
        
        /**
         * A method to calculate the square's temperature in °F
         * 
         * @return      the temperature in °F
         *                      |result == ((1.8)*(this.getTemperature() +32)
         */
        public double getFahrenheit()
        {

                return ((1.8)*(this.getTemperature()+32));
        }
        
        /**
         * A method to calculate the square's temperature in °K
         * 
         * @return      the temperature in °K
         *                      |result == (this.getTemperature() + 273)
         * 
         */
        public double getKelvin(){                      
                        return (this.getTemperature() + 273);
        }
        
        /** 
         * A method to calculate the amount of cold damage inflicted by the square where the creature resides
         * 
         * @return      The amount of cold damage represented as an integer
         *                      |if(this.getTemperature() >= (this.getCOLD_DAMAGE_LIMIT()-this.getCOLD_DAMAGE_AMOUNT()+1))
         *                      |       result == 0
         *                      |double coldDamage = (Math.abs(this.getTemperature())-Math.abs(this.getCOLD_DAMAGE_LIMIT)))
         *                      |                                                       /this.getCOLD_DAMAGE_AMOUNT()
         *                      |result == (int) coldDamage
         *                      |
         */
        public int coldDamage(){
                if(this.getTemperature()>=(this.getCOLD_DAMAGE_LIMIT()-this.getCOLD_DAMAGE_AMOUNT()+1))
                        return 0;
                double coldDamage = (Math.abs(this.getTemperature())-Math.abs(this.getCOLD_DAMAGE_LIMIT()))
                                                                /(this.getCOLD_DAMAGE_AMOUNT());
                return (int) coldDamage;
        }
        
        

        /**
         * A method to calculate the amount of heat damage inflicted by the square where the creature resides
         * 
         * @return      The amount of heat damage represented as an integer
         *                      |if(this.getTemperature()<this.getHEAT_DAMAGE_LIMIT()+
         *                      |                               this.getHEAT_DAMAGE_CUTOFF())
         *                      |       result == 0
         *                      |double heatDamage = (this.getTemperature()-this.getHEAT_DAMAGE_LIMIT())
         *                      |                                               /this.getHEAT_DAMAGE_AMOUNT()
         *                      |result == (int) heatDamage
         */
        public int heatDamage(){
                if(this.getTemperature()<this.getHEAT_DAMAGE_LIMIT()+
                                this.getHEAT_DAMAGE_AMOUNT())
                        return 0;
                double heatDamage = ((this.getTemperature()-this.getHEAT_DAMAGE_LIMIT()))
                                                                /this.getHEAT_DAMAGE_AMOUNT();
                return (int) heatDamage;
                                
        }
        
        /**
         * A method to calculate the amount of rust damage inflicted by the square
         * 
         * @return       the amount of rust damage
         *                      |if(this.getHumidity() < (this.getRUST_DAMAGE_LIMIT()+this.getRUST_DAMAGE_AMOUNT())
         *                      |       result == 0
         *                      |
         *                      |                                       
         *                      |result == (this.getHumidity()-this.getRUST_DAMAGE_LIMIT())
         *                      |               /(this.getRUST_DAMAGE_AMOUNT()

         * 
         */
        public int rustDamage(){
                
                
                int thishumidity = this.getHumidity().multiply(new BigDecimal("100")).intValue();
                int thisRustDamageLimit = this.getRUST_DAMAGE_LIMIT().multiply(new BigDecimal("100")).intValue();
                int thisRustDamageAmount = this.getRUST_DAMAGE_AMOUNT().multiply(new BigDecimal("100")).intValue();
                if(thishumidity<thisRustDamageAmount+thisRustDamageLimit)
                        return 0;
                int rustDamage = (thishumidity - thisRustDamageLimit)/thisRustDamageAmount;
                return rustDamage;
                
        }
        
        /**
         * A variable to store the cold damage limit
         */

        private final static double COLD_DAMAGE_LIMIT=-5;
        
        /**
         * A variable to store the cold damage amount
         */
        private final static double COLD_DAMAGE_AMOUNT=10;
        
        /**
         * A method to get the cold damage limit
         * @return      the cold damage limit
         *                      |result == this.COLD_DAMAGE_LIMIT;
         */
        @Basic @Immutable
        public double getCOLD_DAMAGE_LIMIT(){
                return Square.COLD_DAMAGE_LIMIT;
        }
        
        /**
         * A method to get the cold damage amount
         * @return  the cold damage amount
         *                      |result == this.COLD_DAMAGE_AMOUNT
         */
        @Basic @Immutable
        public double getCOLD_DAMAGE_AMOUNT(){
                return Square.COLD_DAMAGE_AMOUNT;
        }
                
        /**
         * A variable to store the heat damage limit
         */
        //The heat damage limit is the temperature from which above
        //the square is 'in the danger zone' to inflict heat damage'
        private static double HEAT_DAMAGE_LIMIT= 35;
        
        /**
         * A variable to store the heat damage amount
         */
        //The heat damage amount is the amount of degrees above the
        //heat damage limit you have to be to get heat damage
        private static double HEAT_DAMAGE_AMOUNT= 15;
        
        /**
         * A method to get the heat damage limit
         * @return      the heat damage limit
         *                      |result == this.HEAT_DAMAGE_LIMIT;
         */
        @Basic
        public double getHEAT_DAMAGE_LIMIT(){
                return Square.HEAT_DAMAGE_LIMIT;
        }
        
        /**
         * A method to get the heat damage amount
         * @return  the heat damage amount
         *                      |result == this.HEAT_DAMAGE_AMOUNT
         */
        @Basic
        public double getHEAT_DAMAGE_AMOUNT(){
                return Square.HEAT_DAMAGE_AMOUNT;
        }
        
        /**
         * A method to set the parameters for calculating the heat damage
         * 
         * @param       limit
         *                      the heat damage limit you wish to set
         * @param       amount
         *                      the heat damage amount you wish to set
         * @post        the heat damage limit will be set to the desired value 
         *                      |new.getHEAT_DAMAGE_LIMIT() = limit 
         * @post        the heat damage amount will be set to the desired value 
         *                      |new.getHEAT_DAMAGE_AMOUNT() = amount
         * @throws      IllegalArgumentException
         *                      If the amount is not positive or equal to zero and bigger than the difference between maximumtemperature and the limit
         *                      |if(!(amount >= 0 && amount <= this.getMAX_TEMPERATURE()-limit))
         * @throws  IllegalTemperatureException
         *                      If the heat damage limit is not a valid temperature
         *                      |!isValidTemperature(limit)
         */
        public void setHeatDamageParameters(double limit, double amount) throws IllegalArgumentException, IllegalTemperatureException{
                if(!(isValidTemperature(limit)))
                        throw new IllegalTemperatureException(limit, this);
                if(!(amount >= 0 && amount <= (this.getMAX_TEMPERATURE()-limit)))
                        throw new IllegalArgumentException();
                Square.HEAT_DAMAGE_LIMIT =limit;
                Square.HEAT_DAMAGE_AMOUNT = amount;
        }
        
        /**
         * A variable registering the humidity of the square
         */
        private BigDecimal humidity;
        
        /**
         * A method returning the current humidity of the square
         */
        @Basic
        public BigDecimal getHumidity() {
                return this.humidity;
        }
        
        
        /**
         * A method to set the humidity to the desired value
         * 
         * @param       humidity
         * @pre         the humidity has to be a valid percentage
         *                      |isValidHumidity(humidity)
         * @post        the humidity of the square will be set to the valid humidity
         *                      |(new.getHumidity() = humidity
         * @post        the scale of the humidity will be set to 2 digits after the comma
         *                      |new.humidity.scale() = 2
         */
        public void setHumidity(BigDecimal humidity) {

                assert isValidHumidity(humidity);
                humidity.setScale(2,BigDecimal.ROUND_HALF_EVEN);  
                this.humidity = humidity;      
        }
        
        /**
         * A method to check whether the desired humidity is a valid humidity value
         * 
         * @param       humidity
         *                      the humidity you want to assign to the square
         * @return      true if the humidity is between 0 and 100 and not null
         *                      |(if(0 <= humidity && humidity <=100)
         *                      |       result == true
         *                      |if(humidity == null)
         *                      |       result == false
         */
        
        public boolean isValidHumidity(BigDecimal humidity){
        
                if(this.getMINIMUM_HUMIDITY().compareTo(humidity)==-1)
                        if(this.getMAXIMUM_HUMIDITY().compareTo(humidity)==1)
                                        return true;
                if(humidity==null)
                        return false;
                return false;           
        }
        
        /**
         * A variable to store the maximum humidity
         */
        
        private static BigDecimal MAXIMUM_HUMIDITY = new BigDecimal("1.0");
        
        /**
         * A variable to store the minimum humidity
         */
        
        private static BigDecimal MINIMUM_HUMIDITY = new BigDecimal("0.00");
        
        /**
         * A method to get the maximum humidity of squares
         */
        @Basic
        public BigDecimal getMAXIMUM_HUMIDITY(){
                return Square.MAXIMUM_HUMIDITY;
        }
        
        /**
         * A method to get the minimum humidity of squares
         */
        @Basic
        public BigDecimal getMINIMUM_HUMIDITY(){
                return Square.MINIMUM_HUMIDITY;
        }
        
        /**
         * A variable to store the rust damage limit
         */
        //The rust damage limit is the humidity from which above
        //the square is 'in the danger zone' to inflict rust damage
        private BigDecimal RUST_DAMAGE_LIMIT = new BigDecimal("0.30");

        /**
         * A variable to store the rust damage amount
         */
        //the rust damage amount is the amount of humidity above the
        //rust damage limit you have to be to get rust damage
        private BigDecimal RUST_DAMAGE_AMOUNT = new BigDecimal("0.07");
        
        /**
         * A method to get the rust damage limit
         * @return      the rust damage limit
         *                      |result == this.RUST_DAMAGE_LIMIT;
         */
        @Basic
        public BigDecimal getRUST_DAMAGE_LIMIT(){
                return this.RUST_DAMAGE_LIMIT;
        }
        
        /**
         * A method to get the rust damage amount
         * @return  the rust damage amount
         *                      |result == this.RUST_DAMAGE_AMOUNT
         */
        @Basic
        public BigDecimal getRUST_DAMAGE_AMOUNT(){
                return this.RUST_DAMAGE_AMOUNT;
        }
        
        
        /**
         * A method to set the parameters for calculating the rust damage
         * 
         * @param       limit
         *                      the rust damage limit you wish to set
         * @param       amount
         *                      the rust damage amount you wish to set
         * @pre         the rust damage limit has to be a valid humidity
         *                      |isValidHumidity(limit)
         * @pre         the rust damage amount has to be a valid humidity 
         *                      |isValidHumidity(amount) 
         * @pre         the rust damage amount has to be smaller than the difference between maximum humidity and the limit
         *                      |this.getMAXIMUM_HUMIDITY().subtract(limit)).compareTo(amount))==1
         * @post        the rust damage limit will be set to the desired value 
         *                      |new.getRUST_DAMAGE_LIMIT() = limit 
         * @post        the rust damage amount will be set to the desired value 
         *                      |new.getRUST_DAMAGE_AMOUNT() = amount
         */
        public void setRustDamageParameters(BigDecimal limit, BigDecimal amount) {
                assert(isValidHumidity(limit));
                assert(isValidHumidity(amount));
                assert(((this.getMAXIMUM_HUMIDITY().subtract(limit)).compareTo(amount))==1);
                this.RUST_DAMAGE_LIMIT = limit;
                this.RUST_DAMAGE_AMOUNT=amount;
                this.RUST_DAMAGE_LIMIT.setScale(2);
                this.RUST_DAMAGE_AMOUNT.setScale(2);
                }
        
        /**
         * A variable to keep track of the amount of slipperiness of the square
         */
        private Slippery slippery;
        
        /**
         * Returns the amount of slipperiness
         */
        @Basic
        public Slippery getSlippery(){
                return this.slippery;
        }
        
        /**
    	 * A variable to store of which material the square is made
    	 */
    	private boolean slipperyMaterial = true;
    	
    	/**
    	 * Return the square material
    	 */
    	@Basic
    	public boolean getMaterial(){
    		return this.slipperyMaterial;
    		
    	}
        
        /**
    	 * A method to set the material of the square
    	 * 
    	 * @post	The material of the square will be set to the specified material
    	 * 			|new.getMaterial() = material
    	 * @throws 	IllegalArgumentException
    	 * 			If the material is not specified as a valid material
    	 * 			|!isValidMaterial(material)
    	 */
    	public void setMaterial(boolean material) throws IllegalArgumentException{
    		if(!this.isValidMaterial(material))
    			throw new IllegalArgumentException();
    		this.slipperyMaterial = material;
    	}
    	
    	/**
    	 * A method to check whether the square material is valid
    	 * 
    	 * @param 	material
    	 * 			the material you want to check
    	 * @return	true if the material is valid
    	 * 			|if(material==true || material ==false)
    	 * 			|	result == true
    	 * 			|result == false;
    	 */
    	public boolean isValidMaterial(boolean material){
    		if(material==true || material ==false)
    			return true;
    		return false;
    	}


    	/**
    	 * A method to set the parameter slippery
    	 * 
    	 * @post	if the square is made of a slippery material the square is slippery
    	 *			|if(this.getMaterial() == true)
    	 *			|	new.getSlipper() = Slippery.SLIPPERY_MATERIAL
    	 * @post	if the humidity is 100% and the temperature positive the 
    	 * 				slippery will be water slipperiness
    	 * 			|if(this.getHumidity().compareTo(this.getMAXIMUM_HUMIDITY())==0
    	 * 			|	&& this.getTemperature()>0)	
    	 * 			|		new.getSlippery() = Slippery.WATER_SLIPPERYNESS
    	 * @post	if the humidity is more than 10% and the temperature is smaller or equal to 0°C
    	 * 				the slippery will be and ice layer
    	 * 			|if(this.getHumidity().compareTo(new BigDecimal("0.10"))==1
    	 *			|	&& this.getTemperature()<=0)			
    	 *			|		new.getSlippery() = Slippery.ICE_LAYER
    	 * @post	if the humidity is lower than 10% the slippery will be not slippery
    	 * 			|if(this.getHumidity().compareTo(new BigDecimal("0.10"))==-1)
    	 *			|	new.getSlippery() = Slippery.NOT_SLIPPERY
    	 *
    	 */
    	public void setSlippery() {
    		if(this.getMaterial()==true ){
    			this.slippery = Slippery.SLIPPERY_MATERIAL;
    		}
    		else if(this.getHumidity().compareTo(this.getMAXIMUM_HUMIDITY()) == 0 
    				&& this.getTemperature()>0){
    				this.slippery = Slippery.WATER_SLIPPERYNESS;		
    		}
    		else if(this.getHumidity().compareTo(new BigDecimal("0.10"))==1 
    				&& this.getTemperature()<=0){
    				this.slippery = Slippery.ICE_LAYER;
    		}
    		else{
    			this.slippery = Slippery.NOT_SLIPPERY;
    		}
    		
    			
    	}

        /**
         * A variable to set the borders
         */
        private List<Integer> borders = new ArrayList<Integer>(1);

        /**
         * A method to set borders in one of 6 directions
         * 
         * @param int direction the direction in which you want to set a border
         * 
         * @post if the border is a valid border for this square, the square will
         *       have a new border with the same value as the parameter 
         *       {direction in directions | isValidBorder(direction) : new.hasborder(direction))
         *       
         */

        public void setBorder(int... directions) {
                for(int direction:directions)
                if (this.hasNumberBorders() < 6
                                && this.isValidBorder(direction) == true) {
                        borders.add(direction);
                }
        }

        /**
         * A method to know how many borders a square has
         * 
         * @return number of borders
         */

        public int hasNumberBorders() {
                if (borders.isEmpty() == true)
                        return 0;
                return this.borders.size();
        }

        /**
         * A method to check if a direction is valid as a border for this square
         * 
         * @param direction
         *                the direction in which you wish to set a border for this square
         * @return true if the direction is between 0 and 7 (not 0 or 7)
         *                          |if (border >0 && <7) 
         *                              |new.isValidBorder == true
         * @return false if the square already had a border in that direction 
         *                              |if (this.hasBorder == true) 
         *                              |new.isValidBorder == false
         * 
         */

        public boolean isValidBorder(int direction) {
                Boolean isValid = true;
                if (direction < 1 || direction > 6) {
                        isValid = false;
                }
                if (this.hasBorder(direction) == true) {
                        isValid = false;
                }
                return isValid;
        }

        /**
         * A method to know if a square already had a border in a specified direction
         * 
         * @param direction
         *                the direction in which you wish to know if the square has a border
         * @return true if the square has a border in this direction
         * 
         */

        public boolean hasBorder(int direction) {
                return this.borders.contains(direction);
        }

        /**
         * A method to return the borders of a square
         * 
         * @return the borders of the square
         */
        @Basic
        public List<Integer> getBorders(){
                return borders;
        }
        
        /**
         * A method to delete a border in the specified direction
         * 
         * @param direction
         *                the direction in which you wish to delete a border from this square
         * @post if the square has a border in this direction, this border will be
         *       deleted |if this.hasBorder(direction) == true |
         *       new.hasBorder(direction) == false
         * 
         * 
         */

        public void deleteBorder(int direction) {
                this.borders.remove(borders.indexOf(direction));
        }

        /**
         * A method for this square to merge with another square
         * 
         * @param square
         *                the square which you wish to merge this square with
         * @param direction
         *                the direction in which you wish to merge
         * @post if this square and the other square both have a border in this
         *       direction, they will merge together and both these borders will be
         *       deleted. 
         *       |if this.hasBorder(direction) == true 
         *       |&& square.hasBorder(direction) == true 
         *       |then new.hasBorder(direction) == false 
         *       |&& new square.hasBorder(direction) == false
         * @post if this square and the other square both have a border in this
         *       direction, they will merge together and they will have the same
         *       humidity which is the average of the two humidities of the single
         *       squares. 
         *       |if this.hasBorder(direction) == true
         *       |&& square.hasBorder(direction) == true 
         *       |then new.getHumidity() ==(this.getHumidity() + square.getHumidity())/2 | && new
         *       |square.getHumidity() == (this.getHumidity() +
         *       |square.getHumidity())/2
         * @post if this square and the other square both have a border in this
         *       direction, they will merge together and they will have the same
         *       temperature which is the weighted average with weights calculated
         *       in the method calculateWeights() 
         *       |if this.hasBorder(direction) == true 
         *       |&& square.hasBorder(direction) == true 
         *       |then new.getTemperature() == (calculateWeights(this.getHumidity(),square.getHumitdity())[1]*this.getHumidity()+ 
         *       |calculateWeights(this.getHumidity(),square.getHumitdity())[2]*square.getHumidity() 
         *       |/((calculateWeights(this.getHumidity(),square.getHumitdity())[1]
         *       |+(calculateWeights(this.getHumidity(),square.getHumidity())[2])
         *       
         * @throws IllegalArgumentException 
         *                 if the two squares can't merge
         *                 |!canHaveForMerge(this,square)
         * 
         */
        public void mergeWith(Square square, int direction) throws IllegalArgumentException {
                if (!this.canHaveForMerge(square, direction)){
                        throw new IllegalArgumentException();
                }
                        
                        this.deleteBorder(direction);
                        square.deleteBorder(direction);
                        Double newTemperature = calculateNewTemperature(this, square);
                        BigDecimal newHumidity = (this.getHumidity().add(square.getHumidity())).divide(new BigDecimal("2.00"));
                        this.setTemperature(newTemperature);
                        square.setTemperature(newTemperature);
                        this.setHumidity(newHumidity);
                        square.setHumidity(newHumidity);
                        this.setSlippery();
                        square.setSlippery();
        
        }
        
        /**
         * A method to check whether or not it is allowed for two squares to merge
         * 
         * @param square
         *                the square which you wish to merge this square with
         * @param direction
         *                the direction in which you wish to merge
         * @return false if the square you wish to merge this square with is null
         *                 |if(square == null)
         *                 |return false
         *                 false if one of these two squares hasn't got a border in the specified direction
         *                 |if (this.hasBorder(direction) !=  true || square.hasBorder(direction) != true)
         *                 |return false
         *                 false if one of these two square hasn't got a humidity
         *                 |if (this.getHumidity() == null || square.getHumidity() == null)
         *                 |return false;       
         */
        public boolean canHaveForMerge (Square square, int direction){
                if(square == null)
                        return false;
                if (this.hasBorder(direction) !=  true || square.hasBorder(direction) != true)
                        return false;
                if (this.getHumidity() == null || square.getHumidity() == null)
                        return false;
                return true;
        }
 
        /**
         * A method to calculate the new temperature after merging
         * 
         * @param square
         * @param square2
         * @return the new temperature
         */

        private Double calculateNewTemperature(Square square,
                        Square square2) {
                double newTemperature = 0;
                try{
                double w1 = calculateWeights(square.getHumidity(), square2.getHumidity())[0].doubleValue();
                double w2 = calculateWeights(square.getHumidity(), square2.getHumidity())[1].doubleValue();
                newTemperature = ((w1 * square.getTemperature()) + (w2 * square2.getTemperature())) / (w1+w2);
                }
                catch (Exception exception){
                newTemperature = 0;     
                }
                
        return newTemperature;
        }

        /**
         * A variable to store the weightconstant
         */
        private static BigDecimal weightConstant = new BigDecimal("0.20");
        
        /**
         * Returns the weightconstant of the square
         */
        @Basic
        public BigDecimal getWeightConstant(){
                return Square.weightConstant;
        }


        /**
         * A method to set the weightConstant
         * 
         * @param constant
         *                the value at which you wish to set the weightconstant
         * @post sets the weightConstant at the value of the constant, if constant
         *       is valid 
         *       |if isValidWeightConstant(constant) == true 
         *       |then new.getWeightConstant() == constant
         * @throws IllegalArgumentException
         *                 if the weightconstant is not valid
         *                 | !isValidWeightConstant(constant)
         */

        public static void setWeightConstant(BigDecimal constant)throws IllegalArgumentException {
                if (isValidWeightConstant(constant) != true) {
                        throw new IllegalArgumentException();
                        }
                        weightConstant = constant;
                
        }

        /**
         * A method to check if a constant is valid as weightConstant
         * 
         *@param  constant
         *                the value you wish to check for a weight constant
         *@return If constant is null the result is false
         *                |if(constant==null )
         *                |result == false
         *                If the constant lies between 0.10 and 0.40 the result is true
         *                |if(0.10 <= constant && constant <= 0.40)
         *                |result == true
         */

        private static boolean isValidWeightConstant(BigDecimal constant) {
                if (constant == null)
                        return false;
                BigDecimal under = new BigDecimal("0.10");
                BigDecimal over = new BigDecimal("0.40");
                if (constant.compareTo(under) != -1)
                        if (constant.compareTo(over) != 1)
                                return true;
                return false;
        }

        /**
         * A method to calculate the weight used to calculate the temperature of two merged squares
         * 
         * @param       humidity1
         *                      the humidity of square 1
         * @param       humidity2
         *                      the humidity of square 2
         * @return      an array containing the weights of respectively this square and the other square
         *                      |result == 
         * @throws      IllegalArgumentException 
         *                      if one or both the humidities are null
         *                      |if(!isValidHumidity(humidity1) || !isValidHumidity(humidity2)
         */

        public BigDecimal[] calculateWeights(BigDecimal humidity1, BigDecimal humidity2)throws IllegalArgumentException {
        
                if (humidity1 == null || humidity2 == null){
                        throw new IllegalArgumentException();
                }
                BigDecimal humidityA = humidity1.add(humidity2).divide(new BigDecimal("2.00"));
                BigDecimal sum = new BigDecimal("2.00").subtract(new BigDecimal("2.00").multiply(this.getWeightConstant()));
                BigDecimal a = sum.divide(new BigDecimal("2.00"));
                BigDecimal weight1 = a.multiply(humidity1).divide(humidityA,new MathContext((4),RoundingMode.HALF_EVEN)).add(this.getWeightConstant());
                BigDecimal weight2 = a.multiply(humidity2).divide(humidityA,new MathContext((4),RoundingMode.HALF_EVEN)).add(this.getWeightConstant());         
                BigDecimal[] weights =  {weight1.setScale(4,RoundingMode.HALF_EVEN), weight2.setScale(4,RoundingMode.HALF_EVEN)};
                return weights;
        }
        
        /**
          * A method to get the inhabitability of a square
          * 
          * @return     the degree of inhabitability of the square
          *             |result == -Math.sqrt((this.heatDamage())³)/
          *             |       Math.sqrt(101-this.getHumidity()*100)-Math.sqrt(this.Coldamage())
          */
        public double getInhabitability(){
                
                double heat = this.heatDamage()*this.heatDamage()*this.heatDamage();
                double cold = this.coldDamage();
                double humidity = (this.getHumidity().doubleValue() * 100);
                double result = -Math.sqrt(heat)/Math.sqrt(101-humidity)-Math.sqrt(cold);
                BigDecimal decimalResult = new BigDecimal(result).setScale(2, RoundingMode.HALF_EVEN);
                
                return decimalResult.doubleValue();
        }

}
